/*
 * dvb_ca.c: generic DVB functions for EN50221 CAM CIMAX interfaces
 *
 * Parts of this file were based on sources as follows:
 *
 * based on code:
 *
 * Copyright (C) 1999-2002 Ralph  Metzler
 *                       & Marcus Metzler for convergence integrated media GmbH
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <linux/kthread.h>

#include "dvb_ca_en50221_cimax.h"
#include "dvb_ringbuffer.h"

#define READ_LPDU_PKT

static int dvb_ca_en50221_debug = 1;

module_param_named(cam_debug, dvb_ca_en50221_debug, int, 0644);
MODULE_PARM_DESC(cam_debug, "enable verbose debug messages");

#define HOST_LINK_BUF_SIZE 0x1000

static int dvb_ca_en50221_link_size = HOST_LINK_BUF_SIZE;
module_param_named(link_size, dvb_ca_en50221_link_size, int, 0644);
MODULE_PARM_DESC(link_size, "debug only, no more than 0x1000");

static int dvb_ca_en50221_buffer_free;
module_param_named(buffer_free, dvb_ca_en50221_buffer_free, int, 0644);
MODULE_PARM_DESC(buffer_free, "debug only");

#define dprintk(args...)\
	do {\
		if (dvb_ca_en50221_debug)\
			printk(args);\
	} while (0)
#define pr_error(fmt, args...) printk("CA EN50211: " fmt, ## args)

#define INIT_TIMEOUT_SECS 40


#define RX_BUFFER_SIZE 65535

#define MAX_RX_PACKETS_PER_ITERATION 10

#define CTRLIF_DATA      0
#define CTRLIF_COMMAND   1
#define CTRLIF_STATUS    1
#define CTRLIF_SIZE_LOW  2
#define CTRLIF_SIZE_HIGH 3

#define CMDREG_HC        1	/* Host control */
#define CMDREG_SW        2	/* Size write */
#define CMDREG_SR        4	/* Size read */
#define CMDREG_RS        8	/* Reset interface */
#define CMDREG_FRIE   0x40	/* Enable FR interrupt */
#define CMDREG_DAIE   0x80	/* Enable DA interrupt */
#define IRQEN (CMDREG_DAIE)

#define STATUSREG_RE     1	/* read error */
#define STATUSREG_WE     2	/* write error */
#define STATUSREG_FR  0x40	/* module free */
#define STATUSREG_DA  0x80	/* data available */
#define STATUSREG_TXERR (STATUSREG_RE|STATUSREG_WE)/* general transfer error */


#define DVB_CA_SLOTSTATE_NONE           0
#define DVB_CA_SLOTSTATE_UNINITIALISED  1
#define DVB_CA_SLOTSTATE_RUNNING        2
#define DVB_CA_SLOTSTATE_INVALID        3
#define DVB_CA_SLOTSTATE_WAITREADY      4
#define DVB_CA_SLOTSTATE_VALIDATE       5
#define DVB_CA_SLOTSTATE_WAITFR         6
#define DVB_CA_SLOTSTATE_LINKINIT       7
#define DVB_CA_SLOTSTATE_WAITLINKINIT   8

#define MAX_CIS_SIZE  512

/* Information on a CA slot */
struct dvb_ca_slot {

	/* current state of the CAM */
	int slot_state;

	/* mutex used for serializing access to one CI slot */
	struct mutex slot_lock;

	/* Number of CAMCHANGES that have occurred since last processing */
	atomic_t camchange_count;

	/* Type of last CAMCHANGE */
	int camchange_type;

	/* base address of CAM config */
	u32 config_base;

	/* value to write into Config Control register */
	u8 config_option;

	/* if 1, the CAM supports DA IRQs */
	u8 da_irq_supported:1;

#ifdef READ_LPDU_PKT
	/* Offset into current ringbuffer when user buffer was not big enough
	   to return entire pkt */
	int rx_offset;
#endif

	/* size of the buffer to use when talking to the CAM */
	int link_buf_size;

	/* buffer for incoming packets */
	struct dvb_ringbuffer rx_buffer;

	/* timer used during various states of the slot */
	unsigned long timeout;
};

/* Private CA-interface information */
struct dvb_ca_private {

	/* pointer back to the public data structure */
	struct dvb_ca_en50221_cimax *pub;

	/* the DVB device */
	struct dvb_device *dvbdev;

	/* Flags describing the interface (DVB_CA_FLAG_*) */
	u32 flags;

	/* number of slots supported by this CA interface */
	unsigned int slot_count;

	/* information on each slot */
	struct dvb_ca_slot *slot_info;

	/* wait queues for read() and write() operations */
	wait_queue_head_t wait_queue;

	/* PID of the monitoring thread */
	struct task_struct *thread;

	/* Flag indicating if the CA device is open */
	unsigned int open:1;

	/* Flag indicating the thread should wake up now */
	unsigned int wakeup:1;

	/* Delay the main thread should use */
	unsigned long delay;

	/* Slot to start looking for data
	to read from in the next user-space read operation */
	int next_read_slot;

	/* mutex serializing ioctls */
	struct mutex ioctl_mutex;

	/*two bufs for read/write*/
	u8 *rbuf;
	u8 *wbuf;
};

static void dvb_ca_en50221_thread_wakeup(struct dvb_ca_private *ca);
static int dvb_ca_en50221_read_data(struct dvb_ca_private *ca,
		int slot, u8 *ebuf, int ecount);
static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca,
		int slot, u8 *ebuf, int ecount);


/**
 * Safely find needle in haystack.
 *
 * @param haystack Buffer to look in.
 * @param hlen Number of bytes in haystack.
 * @param needle Buffer to find.
 * @param nlen Number of bytes in needle.
 * @return Pointer into haystack needle was found at, or NULL if not found.
 */
static char *findstr(char *haystack, int hlen, char *needle, int nlen)
{
	int i;

	if (hlen < nlen)
		return NULL;

	for (i = 0; i <= hlen - nlen; i++) {
		if (!strncmp(haystack + i, needle, nlen))
			return haystack + i;
	}

	return NULL;
}



/* ************************************************************************** */
/* EN50221 physical interface functions */


/**
 * Check CAM status.
 */
static int dvb_ca_en50221_check_camstatus(struct dvb_ca_private *ca, int slot)
{
	int slot_status;
	int cam_present;
	int cam_changed;

	/* IRQ mode */
	if (ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE)
		return atomic_read(&ca->slot_info[slot].camchange_count) != 0;

	/* poll mode */
	slot_status = ca->pub->poll_slot_status(ca->pub, slot, ca->open);

	cam_present = (slot_status & DVB_CA_EN50221_POLL_CAM_PRESENT) ? 1 : 0;
	cam_changed = (slot_status & DVB_CA_EN50221_POLL_CAM_CHANGED) ? 1 : 0;
	if (!cam_changed) {
		int cam_present_old =
			(ca->slot_info[slot].slot_state
				!= DVB_CA_SLOTSTATE_NONE);
		cam_changed = (cam_present != cam_present_old);
	}

	if (cam_changed) {
		if (!cam_present) {
			ca->slot_info[slot].camchange_type =
				DVB_CA_EN50221_CAMCHANGE_REMOVED;
		} else {
			ca->slot_info[slot].camchange_type =
				DVB_CA_EN50221_CAMCHANGE_INSERTED;
		}
		atomic_set(&ca->slot_info[slot].camchange_count, 1);
	} else {
		if ((ca->slot_info[slot].slot_state
			== DVB_CA_SLOTSTATE_WAITREADY) &&
				(slot_status & DVB_CA_EN50221_POLL_CAM_READY)) {
			/* move to validate state if reset is completed */
			ca->slot_info[slot].slot_state =
				DVB_CA_SLOTSTATE_VALIDATE;
		}
	}
	return cam_changed;
}


/**
 * Initialise the link layer connection to a CAM.
 *
 * @param ca CA instance.
 * @param slot Slot id.
 *
 * @return 0 on success, nonzero on failure.
 */
static int dvb_ca_en50221_link_init(struct dvb_ca_private *ca, int slot)
{
	int ret;
	dprintk("%s\n", __func__);

	/* we'll be determining these during this function */
	ca->slot_info[slot].da_irq_supported = 0;
#ifdef READ_LPDU_PKT
	ca->slot_info[slot].rx_offset = 0;
#endif
	/* set the host link buffer size temporarily.
	   it will be overwritten with the real negotiated size later. */
	ca->slot_info[slot].link_buf_size = dvb_ca_en50221_link_size;
	dprintk("negotiate: host(%i)\n", ca->slot_info[slot].link_buf_size);

	ret = ca->pub->negotiate(ca->pub,
		slot, ca->slot_info[slot].link_buf_size);
	if (ret <= 0)
		return ret;

	ca->slot_info[slot].link_buf_size = ret;
	dprintk("Chosen link buffer size of %i\n", ret);

	/* success */
	return 0;
}

/**
 * Read a tuple from attribute memory.
 *
 * @param ca CA instance.
 * @param slot Slot id.
 * @param cis CIS data
 * @param address Address to read from. Updated.
 * @param tupleType Tuple id byte. Updated.
 * @param tupleLength Tuple length. Updated.
 * @param tuple Dest buffer for tuple (must be 256 bytes). Updated.
 *
 * @return 0 on success, nonzero on error.
 */
static int dvb_ca_en50221_read_tuple(struct dvb_ca_private *ca,
		int slot, u8 *cis,
		int *address, int *tupleType, int *tupleLength, u8 *tuple)
{
	int i;
	int _tupleType;
	int _tupleLength;
	int _address = *address;

	/* grab the next tuple length and type */
	_tupleType = cis[_address];
	if (_tupleType < 0)
		return _tupleType;
	if (_tupleType == 0xff) {
		dprintk("END OF CHAIN TUPLE type:0x%x\n", _tupleType);
		*address += 1;
		*tupleType = _tupleType;
		*tupleLength = 0;
		return 0;
	}
	_tupleLength = cis[_address + 1];
	if (_tupleLength < 0)
		return _tupleLength;
	_address += 2;

	dprintk("TUPLE type:0x%x length:%i\n", _tupleType, _tupleLength);

	/* read in the whole tuple */
	for (i = 0; i < _tupleLength; i++) {
		tuple[i] = cis[_address + (i)];
		dprintk("  0x%02x: 0x%02x %c\n",
			i, tuple[i] & 0xff,
			((tuple[i] > 31) && (tuple[i] < 127)) ? tuple[i] : '.');
	}
	_address += (_tupleLength);

	/* success */
	*tupleType = _tupleType;
	*tupleLength = _tupleLength;
	*address = _address;
	return 0;
}


/**
 * Parse attribute memory of a CAM module, extracting Config register,
 * and checking it is a DVB CAM module.
 *
 * @param ca CA instance.
 * @param slot Slot id.
 *
 * @return 0 on success, <0 on failure.
 */
static int dvb_ca_en50221_parse_attributes(struct dvb_ca_private *ca, int slot)
{
	int address = 0;
	int tupleLength;
	int tupleType;
	u8 tuple[257];
	char *dvb_str;
	int rasz;
	int status;
	int got_cftableentry = 0;
	int end_chain = 0;
	int i;
	u16 manfid = 0;
	u16 devid = 0;
	u8 cis[MAX_CIS_SIZE];

	status = ca->pub->read_cis(ca->pub, slot, cis, MAX_CIS_SIZE);
	if (status != 0)
		return -EINVAL;

	/* CISTPL_DEVICE_0A */
	status =
	     dvb_ca_en50221_read_tuple(ca, slot, cis, &address,
		&tupleType, &tupleLength, tuple);
	if (status < 0) {
			pr_error("read status error\r\n");
			return status;
		}
	if (tupleType != 0x1D) {
		pr_error("read tupleType error [0x%x]\r\n", tupleType);
		return -EINVAL;
	}



	/* CISTPL_DEVICE_0C */
	status = dvb_ca_en50221_read_tuple(ca, slot, cis, &address,
			&tupleType, &tupleLength, tuple);

	if (status < 0) {
		pr_error("read read cis error\r\n");
		return -EINVAL;
	}
	if (tupleType != 0x1C) {
		pr_error("read read cis type error\r\n");
		return -EINVAL;
	}



	/* CISTPL_VERS_1 */
	status =
	     dvb_ca_en50221_read_tuple(ca, slot, cis,
		&address, &tupleType, &tupleLength, tuple);
	if (status < 0) {
		pr_error("read read cis  version error\r\n");
		return status;
	}
	if (tupleType != 0x15) {
		pr_error("read read cis version type error\r\n");
		return -EINVAL;
	}



	/* CISTPL_MANFID */
	status = dvb_ca_en50221_read_tuple(ca, slot, cis, &address, &tupleType,
						&tupleLength, tuple);
	if (status < 0) {
		pr_error("read read cis manfid error\r\n");
		return status;
	}
	if (tupleType != 0x20) {
		pr_error("read read cis manfid type error\r\n");
		return -EINVAL;
	}
	if (tupleLength != 4) {
		pr_error("read read cis manfid len error\r\n");
		return -EINVAL;
	}
	manfid = (tuple[1] << 8) | tuple[0];
	devid = (tuple[3] << 8) | tuple[2];



	/* CISTPL_CONFIG */
	status = dvb_ca_en50221_read_tuple(ca, slot, cis, &address, &tupleType,
						&tupleLength, tuple);
	if (status < 0) {
		pr_error("read read cis config error\r\n");
		return status;
	}
	if (tupleType != 0x1A) {
		pr_error("read read cis config type error\r\n");
		return -EINVAL;
	}
	if (tupleLength < 3) {
		pr_error("read read cis config len error\r\n");
		return -EINVAL;
	}

	/* extract the configbase */
	rasz = tuple[0] & 3;
	if (tupleLength < (3 + rasz + 14)) {
		pr_error("read extract the configbase  error\r\n");
		return -EINVAL;
	}
	ca->slot_info[slot].config_base = 0;
	for (i = 0; i < rasz + 1; i++)
		ca->slot_info[slot].config_base |= (tuple[2 + i] << (8 * i));

	/* check it contains the correct DVB string */
	dvb_str = findstr((char *)tuple, tupleLength, "DVB_CI_V", 8);
	if (dvb_str == NULL) {
		pr_error("find dvb str DVB_CI_V  error\r\n");
		return -EINVAL;
	}
	if (tupleLength < ((dvb_str - (char *) tuple) + 12)) {
		pr_error("find dvb str DVB_CI_V len error\r\n");
	    return -EINVAL;
	}

	/* is it a version we support? */
	if (strncmp(dvb_str + 8, "1.00", 4)) {
		pr_info("dvb_ca adapter %d: ", ca->dvbdev->adapter->num);
		pr_info("Unsupported DVB CAM module version %c%c%c%c\n",
			dvb_str[8], dvb_str[9], dvb_str[10], dvb_str[11]);
		return -EINVAL;
	}

	/* process the CFTABLE_ENTRY tuples, and any after those */
	while ((!end_chain) && (address < 0x1000)) {
		status = dvb_ca_en50221_read_tuple(ca, slot, cis, &address,
		&tupleType, &tupleLength, tuple);
		if (status < 0) {
				pr_error("process tuples error\r\n");
			   return status;
			}

		switch (tupleType) {
		case 0x1B:	/* CISTPL_CFTABLE_ENTRY */
			if (tupleLength < (2 + 11 + 17))
				break;

			/* if we've already parsed one, just use it */
			if (got_cftableentry)
				break;

			/* get the config option */
			ca->slot_info[slot].config_option = tuple[0] & 0x3f;

			/* OK, check it contains the correct strings */
			if ((findstr((char *)tuple, tupleLength, "DVB_HOST", 8)
				== NULL)
				|| (findstr((char *)tuple,
					tupleLength, "DVB_CI_MODULE", 13)
					== NULL)) {
				break;
			}
			got_cftableentry = 1;
			break;

		case 0x14:	/* CISTPL_NO_LINK */
			break;

		case 0xFF:	/* CISTPL_END */
			end_chain = 1;
			break;

		default:	/* Unknown tuple type
			-just skip this tuple and move to the next one */
			dprintk("dvb_ca: Skipping unknown tuple type:0x%x",
				tupleType);
			dprintk(" length:0x%x\n", tupleLength);
			break;
		}
	}

	if ((address > 0x1000) || (!got_cftableentry)) {
		pr_error("got_cftableentry :%d\r\n", got_cftableentry);
		return -EINVAL;
	}

	dprintk("Valid DVB CAM detected MANID:%x DEVID:%x", manfid, devid);
	dprintk(" CONFIGBASE:0x%x CONFIGOPTION:0x%x\n",
		ca->slot_info[slot].config_base,
		ca->slot_info[slot].config_option);

	/* success! */
	return 0;
}


/**
 * Set CAM's configoption correctly.
 *
 * @param ca CA instance.
 * @param slot Slot containing the CAM.
 */
static int dvb_ca_en50221_set_configoption(struct dvb_ca_private *ca, int slot)
{
	int configoption;

	dprintk("%s\n", __func__);

	/* set the config option */
	ca->pub->write_cor(ca->pub, slot,
				     ca->slot_info[slot].config_base,
				     &ca->slot_info[slot].config_option);

	configoption = ca->slot_info[slot].config_option;
	dprintk("Set configoption 0x%x, base 0x%x\n",
		ca->slot_info[slot].config_option,
		ca->slot_info[slot].config_base);

	/* fine! */
	return 0;

}


/**
 * This function talks to an EN50221 CAM control interface. It reads a buffer of
 * data from the CAM. The data can either be stored in a supplied buffer, or
 * automatically be added to the slot's rx_buffer.
 *
 * @param ca CA instance.
 * @param slot Slot to read from.
 * @param ebuf If non-NULL, the data will be written to this buffer. If NULL,
 * the data will be added into the buffering system as a normal fragment.
 * @param ecount Size of ebuf. Ignored if ebuf is NULL.
 *
 * @return Number of bytes read, or < 0 on error
 */
static int dvb_ca_en50221_read_data(struct dvb_ca_private *ca,
		int slot, u8 *ebuf, int ecount)
{
	int bytes_read;
	int status;
	u8 *buf = ca->rbuf;

	/* dprintk("%s\n", __func__); */

	/* check if we have space for a link buf in the rx_buffer */
	if (ebuf == NULL) {
		int buf_free;

		if (ca->slot_info[slot].rx_buffer.data == NULL) {
			status = -EIO;
			goto exit;
		}
		buf_free = dvb_ringbuffer_free(&ca->slot_info[slot].rx_buffer);
		dvb_ca_en50221_buffer_free = buf_free;

		if (buf_free < (ca->slot_info[slot].link_buf_size
				+ DVB_RINGBUFFER_PKTHDRSIZE)) {
			status = -EAGAIN;
			goto exit;
		}
	}

	/* check if there is data available */
	status = ca->pub->read_cam_status(ca->pub, slot);
	if (status < 0)
		goto exit;
	if (!(status & STATUSREG_DA)) {
		/* no data */
		status = 0;
		goto exit;
	}

	/* read the amount of data */
	status = ca->pub->read_lpdu(ca->pub,
			slot, buf, dvb_ca_en50221_link_size);
	if (status < 0) {
		ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_LINKINIT;
		status = -EIO;
		goto exit;
	}

	bytes_read = status;

	/* check it will fit */
	if (ebuf == NULL) {
		if (bytes_read > ca->slot_info[slot].link_buf_size) {
			pr_error("dvb_ca adapter %d:",
				ca->dvbdev->adapter->num);
			pr_error(" CAM tried to send a buffer larger ");
			pr_error("than the link buffer size(%i > %i)!\n",
			       bytes_read, ca->slot_info[slot].link_buf_size);
			ca->slot_info[slot].slot_state =
				DVB_CA_SLOTSTATE_LINKINIT;
			status = -EIO;
			goto exit;
		}
		if (bytes_read < 2) {
			pr_error("dvb_ca adapter %d: ",
					ca->dvbdev->adapter->num);
			pr_error("CAM sent a buffer");
			pr_error("that was less than 2B!\n");
			ca->slot_info[slot].slot_state =
				DVB_CA_SLOTSTATE_LINKINIT;
			status = -EIO;
			goto exit;
		}
	} else {
		if (bytes_read > ecount) {
			pr_error("dvb_ca adapter %d:",
				ca->dvbdev->adapter->num);
			pr_error(" CAM tried to send a buffer larger");
			pr_error(" than the ecount size!\n");
			status = -EIO;
			goto exit;
		}
	}

	/* OK, add it to the receive buffer,
	   or copy into external buffer if supplied */
	if (ebuf == NULL) {
		if (ca->slot_info[slot].rx_buffer.data == NULL) {
			status = -EIO;
			goto exit;
		}
		dvb_ringbuffer_pkt_write(&ca->slot_info[slot].rx_buffer,
			buf, bytes_read);
	} else {
		memcpy(ebuf, buf, bytes_read);
	}

#ifndef READ_LPDU_PKT
	/* wake up readers when a last_fragment is received */
	if ((buf[1] & 0x80) == 0x00)
#endif
		wake_up_interruptible(&ca->wait_queue);
	status = bytes_read;

exit:
	return status;
}


/**
 * This function talks to an EN50221 CAM control interface.
 * It writes a buffer of data
 * to a CAM.
 *
 * @param ca CA instance.
 * @param slot Slot to write to.
 * @param ebuf The data in this buffer
 * is treated as a complete link-level packet to be written.
 * @param count Size of ebuf.
 *
 * @return Number of bytes written, or < 0 on error.
 */
static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca,
		int slot, u8 *buf, int bytes_write)
{
	int status;

	/* dprintk("%s\n", __func__); */

	/* sanity check */
	if (bytes_write > ca->slot_info[slot].link_buf_size)
		return -EINVAL;

	/* it is possible we are dealing with a single buffer implementation,
	   thus if there is data available for read or if there is even a read
	   already in progress, we do nothing but awake the kernel thread to
	   process the data if necessary. */
	status = ca->pub->read_cam_status(ca->pub, slot);
	if (status < 0)
		return status;
	if (status & (STATUSREG_DA | STATUSREG_RE)) {
		if (status & STATUSREG_DA)
			dvb_ca_en50221_thread_wakeup(ca);
		status = -EAGAIN;
		return status;
	}

	if (!(status & STATUSREG_FR)) {
		status = -EAGAIN;
		return status;
	}

	status = ca->pub->write_lpdu(ca->pub, slot, buf, bytes_write);
	if (status < 0) {
		if (status == -EBUSY) {
			status = -EAGAIN;
			return status;
		} else {
			ca->slot_info[slot].slot_state =
				DVB_CA_SLOTSTATE_LINKINIT;
			status = -EIO;
			return status;
		}
	}

	status = bytes_write;

	return status;
}
EXPORT_SYMBOL(dvb_ca_en50221_cimax_camchange_irq);



/* ************************************************************************** */
/* EN50221 higher level functions */


/**
 * A CAM has been removed => shut it down.
 *
 * @param ca CA instance.
 * @param slot Slot to shut down.
 */
static int dvb_ca_en50221_slot_shutdown(struct dvb_ca_private *ca, int slot)
{
	dprintk("%s\n", __func__);

	ca->pub->slot_shutdown(ca->pub, slot);
	ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_NONE;

	/* need to wake up all processes to check if they're now
	   trying to write to a defunct CAM */
	wake_up_interruptible(&ca->wait_queue);

	dprintk("Slot %i shutdown\n", slot);

	/* success */
	return 0;
}
EXPORT_SYMBOL(dvb_ca_en50221_cimax_camready_irq);


/**
 * A CAMCHANGE IRQ has occurred.
 *
 * @param ca CA instance.
 * @param slot Slot concerned.
 * @param change_type One of the DVB_CA_CAMCHANGE_* values.
 */
void dvb_ca_en50221_cimax_camchange_irq(struct dvb_ca_en50221_cimax *pubca,
		int slot, int change_type)
{
	struct dvb_ca_private *ca = pubca->private;

	dprintk("CAMCHANGE IRQ slot:%i change_type:%i\n", slot, change_type);

	switch (change_type) {
	case DVB_CA_EN50221_CAMCHANGE_REMOVED:
	case DVB_CA_EN50221_CAMCHANGE_INSERTED:
		break;

	default:
		return;
	}

	ca->slot_info[slot].camchange_type = change_type;
	atomic_inc(&ca->slot_info[slot].camchange_count);
	dvb_ca_en50221_thread_wakeup(ca);
}
EXPORT_SYMBOL(dvb_ca_en50221_cimax_frda_irq);


/**
 * A CAMREADY IRQ has occurred.
 *
 * @param ca CA instance.
 * @param slot Slot concerned.
 */
void dvb_ca_en50221_cimax_camready_irq(struct dvb_ca_en50221_cimax *pubca,
		int slot)
{
	struct dvb_ca_private *ca = pubca->private;

	dprintk("CAMREADY IRQ slot:%i\n", slot);

	if (ca->slot_info[slot].slot_state == DVB_CA_SLOTSTATE_WAITREADY) {
		ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_VALIDATE;
		dvb_ca_en50221_thread_wakeup(ca);
	}
}


/**
 * An FR or DA IRQ has occurred.
 *
 * @param ca CA instance.
 * @param slot Slot concerned.
 */
void dvb_ca_en50221_cimax_frda_irq(struct dvb_ca_en50221_cimax *pubca,
		int slot)
{
	struct dvb_ca_private *ca = pubca->private;
	int flags;

	dprintk("FR/DA IRQ slot:%i\n", slot);

	switch (ca->slot_info[slot].slot_state) {
	case DVB_CA_SLOTSTATE_LINKINIT:
		flags = ca->pub->get_capbility(pubca, slot);
		if (flags & DVB_CA_EN50221_CAP_IRQ) {
			dprintk("CAM supports DA IRQ\n");
			ca->slot_info[slot].da_irq_supported = 1;
		}
		break;

	case DVB_CA_SLOTSTATE_RUNNING:
		if (ca->open)
			dvb_ca_en50221_thread_wakeup(ca);
		break;
	}
}



/* ************************************************************************** */
/* EN50221 thread functions */

/**
 * Wake up the DVB CA thread
 *
 * @param ca CA instance.
 */
static void dvb_ca_en50221_thread_wakeup(struct dvb_ca_private *ca)
{

	dprintk("%s\n", __func__);

	ca->wakeup = 1;
	mb(); /*original*/
	wake_up_process(ca->thread);
}

/**
 * Update the delay used by the thread.
 *
 * @param ca CA instance.
 */
static void dvb_ca_en50221_thread_update_delay(struct dvb_ca_private *ca)
{
	int delay;
	int curdelay = 100000000;
	int slot;

	/* Beware of too high polling frequency, because one polling
	 * call might take several hundred milliseconds until timeout!
	 */
	for (slot = 0; slot < ca->slot_count; slot++) {
		switch (ca->slot_info[slot].slot_state) {
		default:
		case DVB_CA_SLOTSTATE_NONE:
			delay = HZ * 60;  /* 60s */
			if (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE))
				delay = HZ * 5;  /* 5s */
			break;
		case DVB_CA_SLOTSTATE_INVALID:
			delay = HZ * 60;  /* 60s */
			if (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE))
				delay = HZ / 10;  /* 100ms */
			break;

		case DVB_CA_SLOTSTATE_UNINITIALISED:
		case DVB_CA_SLOTSTATE_WAITREADY:
		case DVB_CA_SLOTSTATE_VALIDATE:
		case DVB_CA_SLOTSTATE_WAITFR:
		case DVB_CA_SLOTSTATE_LINKINIT:
			delay = HZ / 10;  /* 100ms */
			break;
		case DVB_CA_SLOTSTATE_WAITLINKINIT:
			delay = HZ * 2;
			break;

		case DVB_CA_SLOTSTATE_RUNNING:
			delay = HZ * 60;  /* 60s */
			if (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE))
				delay = HZ / 10;  /* 100ms */
			if (ca->open) {
				if ((!ca->slot_info[slot].da_irq_supported) ||
				    (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_DA)))
					delay = HZ / 10;  /* 100ms */
			}
			break;
		}

		if (delay < curdelay)
			curdelay = delay;
	}

	ca->delay = curdelay;
}

static int dvb_ca_en50221_slot_process(struct dvb_ca_private *ca, int slot)
{
	int flags;
	int status;
	int pktcount;
	void *rxbuf;

	mutex_lock(&ca->slot_info[slot].slot_lock);

	/*check the cam status + deal with CAMCHANGEs*/
	while (dvb_ca_en50221_check_camstatus(ca, slot)) {
		/* clear down an old CI slot if necessary */
		if (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_NONE)
			dvb_ca_en50221_slot_shutdown(ca, slot);

		/* if a CAM is NOW present, initialise it */
		if (ca->slot_info[slot].camchange_type
				== DVB_CA_EN50221_CAMCHANGE_INSERTED)
			ca->slot_info[slot].slot_state =
				DVB_CA_SLOTSTATE_UNINITIALISED;

		/* we've handled one CAMCHANGE */
		dvb_ca_en50221_thread_update_delay(ca);
		atomic_dec(&ca->slot_info[slot].camchange_count);
	}

	/* CAM state machine */
	switch (ca->slot_info[slot].slot_state) {
	case DVB_CA_SLOTSTATE_NONE:
	case DVB_CA_SLOTSTATE_INVALID:
		/* no action needed */
		break;

	case DVB_CA_SLOTSTATE_UNINITIALISED:
		ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_WAITREADY;
		ca->pub->slot_reset(ca->pub, slot);
		ca->slot_info[slot].timeout = jiffies
			+ (INIT_TIMEOUT_SECS * HZ);
		break;

	case DVB_CA_SLOTSTATE_WAITREADY:
		if (time_after(jiffies, ca->slot_info[slot].timeout)) {
			dprintk("%d: PC card did not respond\n",
				ca->dvbdev->adapter->num);
			ca->slot_info[slot].slot_state =
				DVB_CA_SLOTSTATE_INVALID;
			dvb_ca_en50221_thread_update_delay(ca);
			break;
		}
		/* no other action needed; will automatically
		 * change state when ready
		 */
		break;

	case DVB_CA_SLOTSTATE_VALIDATE:
		if (dvb_ca_en50221_parse_attributes(ca, slot) != 0) {
			/* we need this extra check
			   for annoying interfaces like the budget-av */
			if ((!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE))
					&& (ca->pub->poll_slot_status)) {
				status = ca->pub->poll_slot_status(ca->pub,
						slot, 0);
				if (!(status
					& DVB_CA_EN50221_POLL_CAM_PRESENT)) {
					ca->slot_info[slot].slot_state =
						DVB_CA_SLOTSTATE_NONE;
					dvb_ca_en50221_thread_update_delay(ca);
					break;
				}
			}
			dprintk(" %d: Invalid PC card inserted :(\n",
			       ca->dvbdev->adapter->num);
			ca->slot_info[slot].slot_state =
					DVB_CA_SLOTSTATE_INVALID;
			dvb_ca_en50221_thread_update_delay(ca);
			break;
		}
		if (dvb_ca_en50221_set_configoption(ca, slot) != 0) {
			dprintk("%d: Unable initialise CAM:(\n",
			       ca->dvbdev->adapter->num);
			ca->slot_info[slot].slot_state =
					DVB_CA_SLOTSTATE_INVALID;
			dvb_ca_en50221_thread_update_delay(ca);
			break;
		}

		if (ca->pub->cam_reset(ca->pub, slot) != 0) {
			dprintk("%d: Unable to reset CAM IF\n",
			       ca->dvbdev->adapter->num);
			ca->slot_info[slot].slot_state =
					DVB_CA_SLOTSTATE_INVALID;
			dvb_ca_en50221_thread_update_delay(ca);
			break;
		}

		dprintk("DVB CAM validated successfully\n");

		ca->slot_info[slot].timeout =
				jiffies + (INIT_TIMEOUT_SECS * HZ);
		ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_WAITFR;
		ca->wakeup = 1;
		break;

	case DVB_CA_SLOTSTATE_WAITFR:
		if (time_after(jiffies, ca->slot_info[slot].timeout)) {
			pr_error("dvb_ca adapter %d: ",
				ca->dvbdev->adapter->num);
			pr_error("DVB CAM did not respond :(\n");
			ca->slot_info[slot].slot_state =
					DVB_CA_SLOTSTATE_INVALID;
			dvb_ca_en50221_thread_update_delay(ca);
			break;
		}

		/*flags = ca->pub->read_cam_status(ca->pub, slot);*/
		flags = STATUSREG_FR;
		if (flags & STATUSREG_FR) {
			ca->slot_info[slot].slot_state =
					DVB_CA_SLOTSTATE_LINKINIT;
			ca->wakeup = 1;
		}
		break;

	case DVB_CA_SLOTSTATE_WAITLINKINIT:
		if (time_after(jiffies, ca->slot_info[slot].timeout)) {
			pr_error("dvb_ca adapter %d: ",
					ca->dvbdev->adapter->num);
			pr_error("DVB CAM link initialisation failed :(\n");
			ca->slot_info[slot].slot_state =
					DVB_CA_SLOTSTATE_INVALID;
			dvb_ca_en50221_thread_update_delay(ca);
			break;
		}

	case DVB_CA_SLOTSTATE_LINKINIT:
		if (dvb_ca_en50221_link_init(ca, slot) != 0) {
			/* we need this extra check for annoying interfaces
			   like the budget-av */
			if ((!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE))
				&& (ca->pub->poll_slot_status)) {
				status = ca->pub->poll_slot_status(ca->pub,
						slot, 0);
				if (!(status
					& DVB_CA_EN50221_POLL_CAM_PRESENT)) {
					ca->slot_info[slot].slot_state =
							DVB_CA_SLOTSTATE_NONE;
					dvb_ca_en50221_thread_update_delay(ca);
					break;
				}
			}

			ca->slot_info[slot].timeout =
					jiffies + (INIT_TIMEOUT_SECS * HZ);
			ca->slot_info[slot].slot_state =
					DVB_CA_SLOTSTATE_WAITLINKINIT;
			ca->wakeup = 1;
			break;
		}

		if (ca->slot_info[slot].rx_buffer.data == NULL) {
			rxbuf = vmalloc(RX_BUFFER_SIZE);
			if (rxbuf == NULL) {
				pr_error("dvb_ca adapter %d: ",
						ca->dvbdev->adapter->num);
				pr_error("Unable to allocate CAM rx buffer\n");
				ca->slot_info[slot].slot_state =
						DVB_CA_SLOTSTATE_INVALID;
				dvb_ca_en50221_thread_update_delay(ca);
				break;
			}
			dvb_ringbuffer_init(&ca->slot_info[slot].rx_buffer,
					rxbuf, RX_BUFFER_SIZE);
		}

		ca->pub->slot_ts_enable(ca->pub, slot);
		ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_RUNNING;
		dvb_ca_en50221_thread_update_delay(ca);
		dprintk("%d: DVB CAM Initialised successfully\n",
		ca->dvbdev->adapter->num);
		break;

	case DVB_CA_SLOTSTATE_RUNNING:
		if (!ca->open)
			break;

		pktcount = 0;
		while ((status = dvb_ca_en50221_read_data(ca, slot, NULL, 0))
			> 0) {
			if (!ca->open)
				break;

			/* if a CAMCHANGE occurred at some point,
			   do not do any more processing of this slot */
			if (dvb_ca_en50221_check_camstatus(ca, slot)) {
				/* we dont want to sleep on the next iteration
				   so we can handle the cam change*/
				ca->wakeup = 1;
				break;
			}

			/* check if we've hit our limit this time */
			if (++pktcount >= MAX_RX_PACKETS_PER_ITERATION) {
				/*dont sleep;
				  there is likely to be more data to read*/
				ca->wakeup = 1;
				break;
			}
		}
		break;
	}

	mutex_unlock(&ca->slot_info[slot].slot_lock);
	return 0;
}


/**
 * Kernel thread which monitors CA slots for CAM changes,
 * and performs data transfers.
 */
static int dvb_ca_en50221_thread(void *data)
{
	struct dvb_ca_private *ca = data;
	int slot;

	dprintk(" %s\n", __func__);
	/* choose the correct initial delay */
	dvb_ca_en50221_thread_update_delay(ca);

	/* main loop */
	while (!kthread_should_stop()) {
		/* sleep for a bit */
		if (!ca->wakeup) {
			set_current_state(TASK_INTERRUPTIBLE);
			schedule_timeout(ca->delay);
			if (kthread_should_stop())
				return 0;
		}
		ca->wakeup = 0;

		/* go through all the slots processing them */
		for (slot = 0; slot < ca->slot_count; slot++)
			dvb_ca_en50221_slot_process(ca, slot);
	}

	return 0;
}



/* ************************************************************************** */
/* EN50221 IO interface functions */

/**
 * Real ioctl implementation.
 * NOTE: CA_SEND_MSG/CA_GET_MSG ioctls have userspace buffers passed to them.
 *
 * @param inode Inode concerned.
 * @param file File concerned.
 * @param cmd IOCTL command.
 * @param arg Associated argument.
 *
 * @return 0 on success, <0 on error.
 */
static int dvb_ca_en50221_io_do_ioctl(struct file *file,
				      unsigned int cmd, void *parg)
{
	struct dvb_device *dvbdev = file->private_data;
	struct dvb_ca_private *ca = dvbdev->priv;
	int err = 0;
	int slot;

	if (mutex_lock_interruptible(&ca->ioctl_mutex)) {
		pr_error("ci lock interrupt error\r\n");
		return -ERESTARTSYS;
	}

	switch (cmd) {
	case CA_RESET:
		dprintk("ci reset---\r\n");
		for (slot = 0; slot < ca->slot_count; slot++) {
			mutex_lock(&ca->slot_info[slot].slot_lock);
			if (ca->slot_info[slot].slot_state
				== DVB_CA_SLOTSTATE_NONE)
				goto next;
			dvb_ca_en50221_slot_shutdown(ca, slot);
			if (ca->flags
				& DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE)
				dvb_ca_en50221_cimax_camchange_irq(
					ca->pub,
					slot,
					DVB_CA_EN50221_CAMCHANGE_INSERTED);
next:
			mutex_unlock(&ca->slot_info[slot].slot_lock);
		}
		ca->next_read_slot = 0;
		dvb_ca_en50221_thread_wakeup(ca);
		break;

	case CA_GET_CAP: {
		struct ca_caps *caps = parg;

		caps->slot_num = ca->slot_count;
		caps->slot_type = CA_CI_LINK;
		caps->descr_num = 0;
		caps->descr_type = 0;
		break;
	}

	case CA_GET_SLOT_INFO: {
		struct ca_slot_info *info = parg;

		if ((info->num > ca->slot_count) || (info->num < 0)) {
			err = -EINVAL;
			pr_error("info num error :%d\r\n", info->num);
			goto out_unlock;
		}

		info->type = CA_CI_LINK;
		info->flags = 0;
		if ((ca->slot_info[info->num].slot_state
				!= DVB_CA_SLOTSTATE_NONE)
			&& (ca->slot_info[info->num].slot_state
				!= DVB_CA_SLOTSTATE_INVALID)) {
			info->flags = CA_CI_MODULE_PRESENT;
		}
		if (ca->slot_info[info->num].slot_state
				== DVB_CA_SLOTSTATE_RUNNING) {
			info->flags |= CA_CI_MODULE_READY;
		}
		break;
	}

	default:
	    pr_error("Invalid cmd :%d\r\n", cmd);
		err = -EINVAL;
		break;
	}

out_unlock:
	mutex_unlock(&ca->ioctl_mutex);
	return err;
}


static int dvb_usercopy__(struct file *file,
		   unsigned int cmd, unsigned long arg,
		   int (*func)(struct file *file,
		   unsigned int cmd, void *arg))
{
  char	  sbuf[128];
  void	  *mbuf = NULL;
  void	  *parg = NULL;
  int	  err  = -EINVAL;

  /*  Copy arguments into temp kernel buffer  */
  switch (_IOC_DIR(cmd)) {
  case _IOC_NONE:
	  /*
	   * For this command, the pointer is actually an integer
	   * argument.
	   */
	  parg = (void *) arg;
	  break;
  case _IOC_READ: /* some v4l ioctls are marked wrong ... */
  case _IOC_WRITE:
  case (_IOC_WRITE | _IOC_READ):
	  if (_IOC_SIZE(cmd) <= sizeof(sbuf)) {
		  parg = sbuf;
	  } else {
		  /* too big to allocate from stack */
		  mbuf = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL);
		  if (NULL == mbuf)
			  return -ENOMEM;
		  parg = mbuf;
	  }

	  err = -EFAULT;
	  if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd)))
		  goto out;
	  break;
  }

  /* call driver */
  if ((err = func(file, cmd, parg)) == -ENOIOCTLCMD)
	  err = -ENOTTY;

  if (err < 0)
	  goto out;

  /*  Copy results into user buffer  */
  switch (_IOC_DIR(cmd))
  {
  case _IOC_READ:
  case (_IOC_WRITE | _IOC_READ):
	  if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
		  err = -EFAULT;
	  break;
  }

out:
  kfree(mbuf);
  return err;
}



/**
 * Wrapper for ioctl implementation.
 *
 * @param inode Inode concerned.
 * @param file File concerned.
 * @param cmd IOCTL command.
 * @param arg Associated argument.
 *
 * @return 0 on success, <0 on error.
 */
static long dvb_ca_en50221_io_ioctl(struct file *file,
				    unsigned int cmd, unsigned long arg)
{
	return dvb_usercopy__(file, cmd, arg, dvb_ca_en50221_io_do_ioctl);
}


/**
 * Implementation of write() syscall.
 *
 * @param file File structure.
 * @param buf Source buffer.
 * @param count Size of source buffer.
 * @param ppos Position in file (ignored).
 *
 * @return Number of bytes read, or <0 on error.
 */
static ssize_t dvb_ca_en50221_io_write(struct file *file,
			const char __user *buf, size_t count, loff_t *ppos)
{
	struct dvb_device *dvbdev = file->private_data;
	struct dvb_ca_private *ca = dvbdev->priv;
	u8 slot, connection_id;
	int status;
	u8 *fragbuf = ca->wbuf;
	int fragpos = 0;
	int fraglen;
	unsigned long timeout;
	int written;

	/* dprintk("%s\n", __func__); */

	/* Incoming packet has a 2 byte header.
	   hdr[0] = slot_id, hdr[1] = connection_id */
	if (count < 2)
		return -EINVAL;

	/* extract slot & connection id */
	if (copy_from_user(&slot, buf, 1))
		return -EFAULT;
	if (copy_from_user(&connection_id, buf + 1, 1))
		return -EFAULT;
	buf += 2;
	count -= 2;

	if (slot >= ca->slot_count)
		return -EFAULT;
	/* check if the slot is actually running */
	if (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_RUNNING)
		return -EINVAL;

	/* fragment the packets & store in the buffer */
	while (fragpos < count) {
		fraglen = ca->slot_info[slot].link_buf_size - 2;
		if (fraglen < 0)
			break;
		if (fraglen > dvb_ca_en50221_link_size - 2)
			fraglen = dvb_ca_en50221_link_size - 2;
		if ((count - fragpos) < fraglen)
			fraglen = count - fragpos;

		fragbuf[0] = connection_id;
		fragbuf[1] = ((fragpos + fraglen) < count) ? 0x80 : 0x00;
		status = copy_from_user(fragbuf + 2, buf + fragpos, fraglen);
		if (status) {
			status = -EFAULT;
			goto exit;
		}

		timeout = jiffies + HZ / 2;
		written = 0;
		while (!time_after(jiffies, timeout)) {
			/* check the CAM hasn't been removed/reset
			   in the meantime */
			if (ca->slot_info[slot].slot_state
					!= DVB_CA_SLOTSTATE_RUNNING) {
				status = -EIO;
				goto exit;
			}

			mutex_lock(&ca->slot_info[slot].slot_lock);
			status = dvb_ca_en50221_write_data(ca,
					slot, fragbuf, fraglen + 2);
			mutex_unlock(&ca->slot_info[slot].slot_lock);
			if (status == (fraglen + 2)) {
				written = 1;
				break;
			}
			if (status != -EAGAIN)
				goto exit;

			msleep(20);
		}
		if (!written) {
			status = -EIO;
			goto exit;
		}

		fragpos += fraglen;
	}
	status = count + 2;

exit:
	return status;
}


/**
 * Condition for waking up in dvb_ca_en50221_io_read_condition
 */
static int dvb_ca_en50221_io_read_condition(struct dvb_ca_private *ca,
					    int *result, int *_slot)
{
	int slot;
	int slot_count = 0;
	int idx;
	size_t fraglen;
	int connection_id = -1;
	int found = 0;
	u8 hdr[2];

	slot = ca->next_read_slot;
	while ((slot_count < ca->slot_count) && (!found)) {
		if (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_RUNNING)
			goto nextslot;

		if (ca->slot_info[slot].rx_buffer.data == NULL)
			return 0;
#ifdef READ_LPDU_PKT
		if (ca->slot_info[slot].rx_offset != 0) {
			*_slot = slot;
			return 1;
		}
#endif
		idx = dvb_ringbuffer_pkt_next(&ca->slot_info[slot].rx_buffer,
				-1, &fraglen);
		while (idx != -1) {
			dvb_ringbuffer_pkt_read(&ca->slot_info[slot].rx_buffer,
					idx, 0, hdr, 2);
			if (connection_id == -1)
				connection_id = hdr[0];
			if ((hdr[0] == connection_id)
#ifndef READ_LPDU_PKT
				&& ((hdr[1] & 0x80) == 0)
#endif
			) {
				*_slot = slot;
				found = 1;
				break;
			}

			idx = dvb_ringbuffer_pkt_next(
				&ca->slot_info[slot].rx_buffer,
				idx,
				&fraglen);
		}

nextslot:
		slot = (slot + 1) % ca->slot_count;
		slot_count++;
	}

	ca->next_read_slot = slot;
	return found;
}


/**
 * Implementation of read() syscall.
 *
 * @param file File structure.
 * @param buf Destination buffer.
 * @param count Size of destination buffer.
 * @param ppos Position in file (ignored).
 *
 * @return Number of bytes read, or <0 on error.
 */
static ssize_t dvb_ca_en50221_io_read(struct file *file, char __user *buf,
				      size_t count, loff_t *ppos)
{
	struct dvb_device *dvbdev = file->private_data;
	struct dvb_ca_private *ca = dvbdev->priv;
	int status;
	int result = 0;
	u8 hdr[2];
	int slot;
	int connection_id = -1;
	size_t idx, idx2;
	int last_fragment = 0;
	size_t fraglen;
	int pktlen;
	int dispose = 0;

#ifdef READ_LPDU_PKT
	int offset;
	u8 flag = 0;
#endif

	/* dprintk("%s\n", __func__); */

	/* Outgoing packet has a 2 byte header.
	   hdr[0] = slot_id, hdr[1] = connection_id */
	if (count < 2)
		return -EINVAL;

	/* wait for some data */
	status = dvb_ca_en50221_io_read_condition(ca, &result, &slot);
	if (status == 0) {
		/* if we're in nonblocking mode, exit immediately */
		if (file->f_flags & O_NONBLOCK)
			return -EWOULDBLOCK;

		/* wait for some data */
		status = wait_event_interruptible(ca->wait_queue,
					  dvb_ca_en50221_io_read_condition
					  (ca, &result, &slot));
	}
	if ((status < 0) || (result < 0)) {
		if (result)
			return result;
		return status;
	}

	idx = dvb_ringbuffer_pkt_next(&ca->slot_info[slot].rx_buffer,
			-1, &fraglen);
	pktlen = 2;
	do {
		if (idx == -1) {
			pr_error("dvb_ca adapter %d: ",
				ca->dvbdev->adapter->num);
			pr_error("BUG: read packet ended");
			pr_error("before last_fragment encountered\n");
			status = -EIO;
			goto exit;
		}
#ifdef READ_LPDU_PKT
		offset = 2 + ca->slot_info[slot].rx_offset;
#endif
		dvb_ringbuffer_pkt_read(&ca->slot_info[slot].rx_buffer,
				idx, 0, hdr, 2);
		if (connection_id == -1)
			connection_id = hdr[0];
#ifdef READ_LPDU_PKT
		flag = hdr[1];
		if (hdr[0] == connection_id) {
			if (pktlen < count) {
				if ((pktlen + fraglen - offset) > (count - 2)) {
					fraglen = (count - 2) - pktlen;
					ca->slot_info[slot].rx_offset +=
									fraglen;
					/* more data for user,
					   but cannot send,
					   so force return to user,
					   rather than dispose of it */
					flag |= 0x80;
				} else {
					ca->slot_info[slot].rx_offset = 0;
					fraglen -= offset;
					dispose = 1;
				}

				status = dvb_ringbuffer_pkt_read_user(
					&ca->slot_info[slot].rx_buffer,
					idx,
					offset,
					buf + pktlen + 2,
					fraglen);
				if (status < 0)
					goto exit;
				pktlen += fraglen;
			}

			last_fragment = 1;
		}
#else
		if (hdr[0] == connection_id) {
			if (pktlen < count) {
				if ((pktlen + fraglen - 2) > count)
					fraglen = count - pktlen;
				else
					fraglen -= 2;

				status = dvb_ringbuffer_pkt_read_user(
					&ca->slot_info[slot].rx_buffer,
					idx,
					2,
					buf + pktlen,
					fraglen);
				if (status < 0)
					goto exit;
				pktlen += fraglen;
			}

			if ((hdr[1] & 0x80) == 0)
				last_fragment = 1;
			dispose = 1;
		}
#endif
		idx2 = dvb_ringbuffer_pkt_next(&ca->slot_info[slot].rx_buffer,
				idx, &fraglen);
		if (dispose)
			dvb_ringbuffer_pkt_dispose(
				&ca->slot_info[slot].rx_buffer, idx);
		idx = idx2;
		dispose = 0;
	} while (!last_fragment);

	hdr[0] = slot;
	hdr[1] = connection_id;
	status = copy_to_user(buf, hdr, 2);
	if (status) {
		status = -EFAULT;
		goto exit;
	}
	status = pktlen;

#ifdef READ_LPDU_PKT
	hdr[0] = flag;
	hdr[1] = 0;

	status = copy_to_user(buf + 2, hdr, 2);
	if (status) {
		status = -EFAULT;
		goto exit;
	}
	status = pktlen + 2;
#endif
exit:
	return status;
}


/**
 * Implementation of file open syscall.
 *
 * @param inode Inode concerned.
 * @param file File concerned.
 *
 * @return 0 on success, <0 on failure.
 */
static int dvb_ca_en50221_io_open(struct inode *inode, struct file *file)
{
	struct dvb_device *dvbdev = file->private_data;
	struct dvb_ca_private *ca = dvbdev->priv;
	int err;
	int i;

	dprintk("%s\n", __func__);

	if (!try_module_get(ca->pub->owner))
		return -EIO;

	err = dvb_generic_open(inode, file);
	if (err < 0) {
		module_put(ca->pub->owner);
		return err;
	}

	for (i = 0; i < ca->slot_count; i++) {

		if (ca->slot_info[i].slot_state == DVB_CA_SLOTSTATE_RUNNING) {
			if (ca->slot_info[i].rx_buffer.data != NULL) {
				/* it is safe to call this here without locks
				   because ca->open == 0.
				   Data is not read in this case */
				dvb_ringbuffer_flush(
					&ca->slot_info[i].rx_buffer);
			}
		}
	}

	ca->open = 1;
	dvb_ca_en50221_thread_update_delay(ca);
	dvb_ca_en50221_thread_wakeup(ca);

	return 0;
}


/**
 * Implementation of file close syscall.
 *
 * @param inode Inode concerned.
 * @param file File concerned.
 *
 * @return 0 on success, <0 on failure.
 */
static int dvb_ca_en50221_io_release(struct inode *inode, struct file *file)
{
	struct dvb_device *dvbdev = file->private_data;
	struct dvb_ca_private *ca = dvbdev->priv;
	int err;

	dprintk("%s\n", __func__);

	/* mark the CA device as closed */
	ca->open = 0;
	dvb_ca_en50221_thread_update_delay(ca);

	err = dvb_generic_release(inode, file);

	module_put(ca->pub->owner);

	return err;
}


/**
 * Implementation of poll() syscall.
 *
 * @param file File concerned.
 * @param wait poll wait table.
 *
 * @return Standard poll mask.
 */
static unsigned int dvb_ca_en50221_io_poll(struct file *file, poll_table *wait)
{
	struct dvb_device *dvbdev = file->private_data;
	struct dvb_ca_private *ca = dvbdev->priv;
	unsigned int mask = 0;
	int slot;
	int result = 0;

	/* dprintk("%s\n", __func__); */

	if (dvb_ca_en50221_io_read_condition(ca, &result, &slot) == 1)
		mask |= POLLIN;

	/* if there is something, return now */
	if (mask)
		return mask;

	/* wait for something to happen */
	poll_wait(file, &ca->wait_queue, wait);

	if (dvb_ca_en50221_io_read_condition(ca, &result, &slot) == 1)
		mask |= POLLIN;

	return mask;
}
EXPORT_SYMBOL(dvb_ca_en50221_cimax_init);

#ifdef CONFIG_COMPAT
static long dvb_ca_en50221_compat_ioctl(struct file *filp,
			unsigned int cmd, unsigned long args)
{
	unsigned long ret;

	args = (unsigned long)compat_ptr(args);
	ret = dvb_ca_en50221_io_ioctl(filp, cmd, args);
	return ret;
}
#endif
static const struct file_operations dvb_ca_fops = {
	.owner = THIS_MODULE,
	.read = dvb_ca_en50221_io_read,
	.write = dvb_ca_en50221_io_write,
	.unlocked_ioctl = dvb_ca_en50221_io_ioctl,
	.open = dvb_ca_en50221_io_open,
	.release = dvb_ca_en50221_io_release,
	.poll = dvb_ca_en50221_io_poll,
	.llseek = noop_llseek,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= dvb_ca_en50221_compat_ioctl,
#endif
};

static struct dvb_device dvbdev_ca = {
	.priv = NULL,
	.users = 1,
	.readers = 1,
	.writers = 1,
	.fops = &dvb_ca_fops,
};


/* ************************************************************************** */
/* Initialisation/shutdown functions */


/**
 * Initialise a new DVB CA EN50221 CIMAX interface device.
 *
 * @param dvb_adapter DVB adapter to attach the new CA device to.
 * @param ca The dvb_ca instance.
 * @param flags Flags describing the CA device (DVB_CA_FLAG_*).
 * @param slot_count Number of slots supported.
 *
 * @return 0 on success, nonzero on failure
 */
int dvb_ca_en50221_cimax_init(struct dvb_adapter *dvb_adapter,
			struct dvb_ca_en50221_cimax *pubca,
			int flags, int slot_count)
{
	int ret;
	struct dvb_ca_private *ca = NULL;
	int i;

	dprintk("%s\n", __func__);

	if (slot_count < 1)
		return -EINVAL;

	/* initialise the system data */
	ca = kzalloc(sizeof(struct dvb_ca_private), GFP_KERNEL);
	if (ca == NULL) {
		ret = -ENOMEM;
		goto error;
	}
	ca->pub = pubca;
	ca->flags = flags;
	ca->slot_count = slot_count;
	ca->slot_info =
		kcalloc(slot_count, sizeof(struct dvb_ca_slot), GFP_KERNEL);
	if (ca->slot_info == NULL) {
		ret = -ENOMEM;
		goto error;
	}
	init_waitqueue_head(&ca->wait_queue);
	ca->open = 0;
	ca->wakeup = 0;
	ca->next_read_slot = 0;
	ca->rbuf = kcalloc(HOST_LINK_BUF_SIZE, 1, GFP_KERNEL);
	ca->wbuf = kcalloc(HOST_LINK_BUF_SIZE, 1, GFP_KERNEL);
	if (ca->rbuf == NULL || ca->wbuf == NULL) {
		ret = -ENOMEM;
		goto error;
	}
	pubca->private = ca;

	/* register the DVB device */
	ret = dvb_register_device(dvb_adapter,
			&ca->dvbdev, &dvbdev_ca, ca, DVB_DEVICE_CA, 0);
	if (ret)
		goto error;

	/* now initialise each slot */
	for (i = 0; i < slot_count; i++) {
		memset(&ca->slot_info[i], 0, sizeof(struct dvb_ca_slot));
		ca->slot_info[i].slot_state = DVB_CA_SLOTSTATE_NONE;
		atomic_set(&ca->slot_info[i].camchange_count, 0);
		ca->slot_info[i].camchange_type =
				DVB_CA_EN50221_CAMCHANGE_REMOVED;
		mutex_init(&ca->slot_info[i].slot_lock);
	}

	mutex_init(&ca->ioctl_mutex);

	if (signal_pending(current)) {
		ret = -EINTR;
		goto error;
	}
	mb();/*original*/

	/* create a kthread for monitoring this CA device */
	ca->thread = kthread_run(dvb_ca_en50221_thread, ca, "kdvb-ca-%i:%i",
				 ca->dvbdev->adapter->num, ca->dvbdev->id);
	if (IS_ERR(ca->thread)) {
		ret = PTR_ERR(ca->thread);
		printk("dvb_ca_init: failed to start kernel_thread (%d)\n",
			ret);
		goto error;
	}
	return 0;

error:
	if (ca != NULL) {
		if (ca->dvbdev != NULL)
			dvb_unregister_device(ca->dvbdev);
		kfree(ca->slot_info);
		kfree(ca->rbuf);
		kfree(ca->wbuf);
		kfree(ca);
	}
	pubca->private = NULL;
	return ret;
}
EXPORT_SYMBOL(dvb_ca_en50221_cimax_release);



/**
 * Release a DVB CA EN50221 CIMAX interface device.
 *
 * @param ca_dev The dvb_struct device_s instance for the CA device.
 * @param ca The associated dvb_ca instance.
 */
void dvb_ca_en50221_cimax_release(struct dvb_ca_en50221_cimax *pubca)
{
	struct dvb_ca_private *ca = pubca->private;
	int i;

	dprintk("%s\n", __func__);

	/* shutdown the thread if there was one */
	kthread_stop(ca->thread);

	for (i = 0; i < ca->slot_count; i++) {
		dvb_ca_en50221_slot_shutdown(ca, i);
		vfree(ca->slot_info[i].rx_buffer.data);
	}
	kfree(ca->slot_info);
	kfree(ca->rbuf);
	kfree(ca->wbuf);
	dvb_unregister_device(ca->dvbdev);
	kfree(ca);
	pubca->private = NULL;
}
